﻿namespace SPTaxonomyClient
{
	#region Usings
	using System;
	using System.Collections.Generic;
	using System.Linq;
	using System.Net;
	using System.Xml.Linq;
	using Taxonomy;
	using TaxonomyClientService;
	#endregion

	/// <summary>
	/// A utility class that exposes helper methods to simplify calls to the TaxonomyClientService.asmx.
	/// </summary>
	public class TermStoreUtility
	{
		#region Fields
		private readonly NetworkCredential _credential;
		private readonly string _baseUrl;
		private Taxonomywebservice _service;
		private Taxonomywebservice Service
		{
			get
			{
				return _service ?? (_service = new Taxonomywebservice
				                               	{
				                               		Credentials = _credential ?? CredentialCache.DefaultCredentials,
													Url = _baseUrl + "/_vti_bin/TaxonomyClientService.asmx"
				                               	});
			}
		}
		#endregion

		#region Constructors

		/// <summary>
		/// Initializes the utility class with a <see cref="System.Net.NetworkCredential"/> object
		/// that will be used for the Web Service access.
		/// </summary>
		/// <param name="credential">An object that represents credentials for a user that has enough rights to either
		/// query or add terms to a TermSet in a TermStore.</param>
		/// <param name="baseUrl">A valid URL that can access the Taxonomy Web Service</param>
		public TermStoreUtility(NetworkCredential credential, string baseUrl)
		{
			_credential = credential;
			// TODO : validate the URL and the ending with a slash
			_baseUrl = baseUrl;
		}
		#endregion

		/// <summary>
		/// NewTerm with an attribute label, attribute clientId and attribute parentTermid
		/// </summary>
		/// <param name="serviceId">The Guid identifier of the Managed Metadata Service</param>
		/// <param name="termSetId">The Guid identifier of the <see cref="TermSet"/></param>
		/// <param name="locale">The locale id of the language of the terms</param>
		/// <param name="termName">The label of the term</param>
		/// <param name="parentTermName">Optionally, the name of the parent term</param>
		public Term AddTermIfNotExist(Guid serviceId, Guid termSetId, int locale, string termName, string parentTermName)
		{
			IList<TermSet> termSets = GetTermSets(serviceId.ToString(), new List<string>(new[] { termSetId.ToString() }));

			if (termSets.Count != 1)
			{
				throw new Exception(
					string.Format("Le Term Store d'identifiant {0} n'existe pas ou n'est pas accessible.", termSetId));
			}

			string parentTermId = null;

			Term term;
			
			if (string.IsNullOrEmpty(parentTermName))
			{
				term = (from t in termSets[0].Terms
					 where t.ParentTerm == null
					 && t.Name.ToUpperInvariant() == termName.ToUpperInvariant()
					 select t).SingleOrDefault();
			}
			else
			{
				parentTermId = (from t in termSets[0].Terms
								where t.Name.ToUpperInvariant() == parentTermName.ToUpperInvariant()
								select t.Id).FirstOrDefault();

				term = (from t in termSets[0].Terms
								  where t.Name.ToUpperInvariant() == termName.ToUpperInvariant()
								  && t.ParentTerm != null
								  && t.ParentTerm.Name.ToUpperInvariant() == parentTermName.ToUpperInvariant()
					 select t).SingleOrDefault();
			}

			if (term == null)
			{
				string result = string.Empty;
				try
				{
					result = Service.AddTerms(serviceId, termSetId, locale, GetNewTermSignature(termName, parentTermId));
					term = new Term(XDocument.Parse(result).Root);
				}
				catch (Exception)
				{
					throw new ApplicationException(result);
				}
			}
			return term;
		}

		/// <summary>
		/// Returns a collection of <see cref="TermSet"/>
		/// </summary>
		/// <param name="serviceId">The Guid of the Managed Metadata Service</param>
		/// <param name="termSetIds">A list of the TermSets IDs (guids) to be returned</param>
		/// <returns></returns>
		public IList<TermSet> GetTermSets(string serviceId, IList<string> termSetIds)
		{
			string termsSetsGuids = termSetIds.Aggregate(string.Empty, (current, termSetId) => current + string.Format("<termSetId>{0}</termSetId>", termSetId));

			string serverTermSetTimeStampXml;
			string retour = Service.GetTermSets(
				string.Format("<termStoreId>{0}</termStoreId>", serviceId),
				termsSetsGuids,
				1033,
				string.Format("<timeStamp>{0}</timeStamp>", DateTime.Now.ToLongDateString()),
				"<version>3</version>",
				out serverTermSetTimeStampXml);

			XDocument document = XDocument.Parse(retour);

			IList<TermSet> termSets = document.Descendants("TS").Select(
				xTs => new TermSet
				{
// ReSharper disable PossibleNullReferenceException
					Id = xTs.Attribute("a9").Value,
					Title = xTs.Attribute("a12").Value,
					Description = xTs.Attribute("a11").Value,
					ContactEmail = xTs.Attribute("a68").Value
// ReSharper restore PossibleNullReferenceException
				}).ToList();

			foreach (XElement xTerm in document.Descendants("T"))
			{
				TermSet parentTermSet = (from termSet in termSets
				                  where termSet.Id == (xTerm.Descendants("TM")).FirstOrDefault().Attribute("a24").Value
				                  select termSet).FirstOrDefault();

				Term parentTerm = null;

				if ((xTerm.Descendants("TM")).FirstOrDefault().Attribute("a25") != null )
				{
					parentTerm = (from t in parentTermSet.Terms
									 where t.Id == (xTerm.Descendants("TM")).FirstOrDefault().Attribute("a25").Value
									 select t).FirstOrDefault();
				}

// ReSharper disable PossibleNullReferenceException
				Term term = new Term { Id = xTerm.Attribute("a9").Value };
// ReSharper restore PossibleNullReferenceException

				foreach (XElement termDef in xTerm.Descendants("TL"))
				{
					term.Definitions.Add(
						new TermDefinition
						{
// ReSharper disable PossibleNullReferenceException
							IsDefaultForLanguage = bool.Parse(termDef.Attribute("a31").Value),
							Value = termDef.Attribute("a32").Value
// ReSharper restore PossibleNullReferenceException
						});
				}
				if (parentTerm != null)
				{
					term.ParentTerm = parentTerm;
				}
				
				term.ParentTermSet = parentTermSet;
				parentTermSet.Terms.Add(term);
			}
			return termSets;

		}

		private static string GetNewTermSignature(string termName, string parentId)
		{
			return string.Format("<newTerms><newTerm label=\"{0}\" clientId=\"{1}\" parentTermId=\"{2}\" /></newTerms>",
				termName, 0, string.IsNullOrEmpty(parentId) ? Guid.Empty.ToString() : parentId );
		}
	}
}
